/*
 * Main include file
 * This file contains various routines and globals
 *
 * Copyright (c) 2010-2015 Red Hat, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met :
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and / or other materials provided with the distribution.
 * 3. Neither the names of the copyright holders nor the names of their contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#pragma once
#include "public.h"
#include "../hidpassthrough/pdo.h"

// If defined, will expose absolute axes as a tablet device only if they
// don't come with mouse buttons.
#define EXPOSE_ABS_AXES_WITH_BUTTONS_AS_MOUSE

EVT_WDF_DRIVER_DEVICE_ADD VIOInputEvtDeviceAdd;

EVT_WDF_INTERRUPT_ISR VIOInputInterruptIsr;
EVT_WDF_INTERRUPT_DPC VIOInputQueuesInterruptDpc;
EVT_WDF_INTERRUPT_ENABLE VIOInputInterruptEnable;
EVT_WDF_INTERRUPT_DISABLE VIOInputInterruptDisable;

typedef UCHAR HID_REPORT_DESCRIPTOR, *PHID_REPORT_DESCRIPTOR;

//
// These are the device attributes returned by the mini driver in response
// to IOCTL_HID_GET_DEVICE_ATTRIBUTES.
//
#define HIDMINI_PID     0x1052
#define HIDMINI_VID     0x1AF4
#define HIDMINI_VERSION 0x0001

typedef struct _tagDynamicArray
{
    PVOID Ptr;
    SIZE_T Size;
    SIZE_T MaxSize;
} DYNAMIC_ARRAY, *PDYNAMIC_ARRAY;

typedef struct virtio_input_event
{
    unsigned short type;
    unsigned short code;
    unsigned long value;
} VIRTIO_INPUT_EVENT, *PVIRTIO_INPUT_EVENT;

struct _tagInputDevice;

typedef struct _tagInputClassCommon
{
// the first byte of a HID report is always report ID
#define HID_REPORT_ID_OFFSET   0
#define HID_REPORT_DATA_OFFSET 1

    // this class's HID report
    PUCHAR pHidReport;
    // size of this class's data in the HID report
    SIZE_T cbHidReportSize;
    // report ID of this class
    UCHAR uReportID;
    // the HID report is dirty and should be sent up
    BOOLEAN bDirty;

    NTSTATUS (*GetFeatureFunc)(struct _tagInputClassCommon *pClass, PHID_XFER_PACKET pFeaturePkt);
    NTSTATUS (*EventToCollectFunc)(struct _tagInputClassCommon *pClass, PVIRTIO_INPUT_EVENT pEvent);
    NTSTATUS (*EventToReportFunc)(struct _tagInputClassCommon *pClass, PVIRTIO_INPUT_EVENT pEvent);
    NTSTATUS(*ReportToEventFunc)
    (struct _tagInputClassCommon *pClass,
     struct _tagInputDevice *pContext,
     WDFREQUEST Request,
     PUCHAR pReport,
     ULONG cbReport);
    VOID (*CleanupFunc)(struct _tagInputClassCommon *pClass);

} INPUT_CLASS_COMMON, *PINPUT_CLASS_COMMON;

#define MAX_INPUT_CLASS_COUNT             5
#define REPORTID_FEATURE_TABLET_MAX_COUNT (MAX_INPUT_CLASS_COUNT + 1)

typedef struct _tagInputDevice
{
    VIRTIO_WDF_DRIVER VDevice;

    WDFINTERRUPT QueuesInterrupt;

    struct virtqueue *EventQ;
    struct virtqueue *StatusQ;

    WDFSPINLOCK EventQLock;
    WDFSPINLOCK StatusQLock;

    PVIRTIO_DMA_MEMORY_SLICED EventQMemBlock;
    PVIRTIO_DMA_MEMORY_SLICED StatusQMemBlock;

    WDFQUEUE IoctlQueue;
    WDFQUEUE HidQueue;

    HID_DESCRIPTOR HidDescriptor;
    HID_DEVICE_ATTRIBUTES HidDeviceAttributes;
    PHID_REPORT_DESCRIPTOR HidReportDescriptor;
    UINT64 HidReportDescriptorHash;

    BOOLEAN bChildPdoCreated;

    // array of pointers to input class descriptors, each responsible
    // for one device class (e.g. mouse)
    PINPUT_CLASS_COMMON InputClasses[MAX_INPUT_CLASS_COUNT];
    ULONG uNumOfClasses;
} INPUT_DEVICE, *PINPUT_DEVICE;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(INPUT_DEVICE, GetDeviceContext)

// PDO_EXTENSION is declared in hidpassthrough\pdo.h
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PDO_EXTENSION, PdoGetExtension)

#define VIOINPUT_DRIVER_MEMORY_TAG (ULONG)'niIV'

// Virtio-input data structures:

enum virtio_input_config_select
{
    VIRTIO_INPUT_CFG_UNSET = 0x00,
    VIRTIO_INPUT_CFG_ID_NAME = 0x01,
    VIRTIO_INPUT_CFG_ID_SERIAL = 0x02,
    VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03,
    VIRTIO_INPUT_CFG_PROP_BITS = 0x10,
    VIRTIO_INPUT_CFG_EV_BITS = 0x11,
    VIRTIO_INPUT_CFG_ABS_INFO = 0x12,
};

struct virtio_input_absinfo
{
    unsigned long min;
    unsigned long max;
    unsigned long fuzz;
    unsigned long flat;
    unsigned long res;
};

struct virtio_input_devids
{
    unsigned short bustype;
    unsigned short vendor;
    unsigned short product;
    unsigned short version;
};

union virtio_input_raw_cfg_data {
    char string[128];
    unsigned char bitmap[128];
    struct virtio_input_absinfo abs;
    struct virtio_input_devids ids;
};

typedef struct virtio_input_cfg_data
{
    unsigned char size;
    union virtio_input_raw_cfg_data u;
} VIRTIO_INPUT_CFG_DATA, *PVIRTIO_INPUT_CFG_DATA;

struct virtio_input_config
{
    unsigned char select;
    unsigned char subsel;
    unsigned char size;
    unsigned char reserved[5];
    union virtio_input_raw_cfg_data u;
};

typedef struct virtio_input_event_with_request
{
    VIRTIO_INPUT_EVENT Event;
    WDFREQUEST Request;
} VIRTIO_INPUT_EVENT_WITH_REQUEST, *PVIRTIO_INPUT_EVENT_WITH_REQUEST;

// Event types
#define EV_SYN             0x00
#define EV_KEY             0x01
#define EV_REL             0x02
#define EV_ABS             0x03
#define EV_MSC             0x04
#define EV_LED             0x11

// Button codes
#define BTN_MOUSE          0x110
#define BTN_LEFT           0x110
#define BTN_RIGHT          0x111
#define BTN_MIDDLE         0x112
#define BTN_SIDE           0x113
#define BTN_EXTRA          0x114
#define BTN_FORWARD        0x115
#define BTN_BACK           0x116
#define BTN_TASK           0x117

#define BTN_JOYSTICK       0x120
#define BTN_GAMEPAD        0x130

#define BTN_TOUCH          0x14a
#define BTN_STYLUS         0x14b

#define BTN_WHEEL          0x150
#define BTN_GEAR_DOWN      0x150
#define BTN_GEAR_UP        0x151

// Relative axis codes
#define REL_X              0x00
#define REL_Y              0x01
#define REL_Z              0x02
#define REL_RX             0x03
#define REL_RY             0x04
#define REL_RZ             0x05
#define REL_HWHEEL         0x06
#define REL_DIAL           0x07
#define REL_WHEEL          0x08
#define REL_MISC           0x09

// Absolute axis codes
#define ABS_X              0x00
#define ABS_Y              0x01
#define ABS_Z              0x02
#define ABS_RX             0x03
#define ABS_RY             0x04
#define ABS_RZ             0x05
#define ABS_THROTTLE       0x06
#define ABS_RUDDER         0x07
#define ABS_WHEEL          0x08
#define ABS_GAS            0x09
#define ABS_BRAKE          0x0a
#define ABS_PRESSURE       0x18
#define ABS_DISTANCE       0x19
#define ABS_TILT_X         0x1a
#define ABS_TILT_Y         0x1b
#define ABS_MISC           0x28

// ABS_MT event codes defined in include/uapi/linux/input-event-codes.h
#define ABS_MT_SLOT        0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X  0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y  0x36 /* Center Y touch position */
#define ABS_MT_TOOL_TYPE   0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID     0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE    0x3a /* Pressure on contact area */
#define ABS_MT_DISTANCE    0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X      0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y      0x3d /* Center Y tool position */

// Synchronization events
#define SYN_REPORT         0x00
#define SYN_MT_REPORT      0x02

// Misc events
#define MSC_TIMESTAMP      0x05

// LED codes
#define LED_NUML           0x00
#define LED_CAPSL          0x01
#define LED_SCROLLL        0x02
#define LED_COMPOSE        0x03
#define LED_KANA           0x04
#define LED_SLEEP          0x05
#define LED_SUSPEND        0x06
#define LED_MUTE           0x07
#define LED_MISC           0x08
#define LED_MAIL           0x09
#define LED_CHARGING       0x0a

NTSTATUS
VIOInputFillEventQueue(PINPUT_DEVICE pContext);

NTSTATUS
VIOInputAddInBuf(IN struct virtqueue *vq, IN PVIRTIO_INPUT_EVENT buf, IN PHYSICAL_ADDRESS pa);

NTSTATUS
VIOInputAddOutBuf(IN struct virtqueue *vq, IN PVIRTIO_INPUT_EVENT buf, IN PHYSICAL_ADDRESS pa);

NTSTATUS
VIOInputBuildReportDescriptor(PINPUT_DEVICE pContext);

VOID EvtIoDeviceControl(WDFQUEUE Queue,
                        WDFREQUEST Request,
                        size_t OutputBufferLength,
                        size_t InputBufferLength,
                        ULONG IoControlCode);

VOID ProcessInputEvent(PINPUT_DEVICE pContext, PVIRTIO_INPUT_EVENT pEvent);

NTSTATUS
ProcessOutputReport(PINPUT_DEVICE pContext, WDFREQUEST Request, PHID_XFER_PACKET pPacket);

NTSTATUS
RequestCopyFromBuffer(WDFREQUEST Request, PVOID SourceBuffer, size_t NumBytesToCopyFrom);

BOOLEAN
DynamicArrayReserve(PDYNAMIC_ARRAY pArray, SIZE_T cbSize);

BOOLEAN
DynamicArrayAppend(PDYNAMIC_ARRAY pArray, PVOID pData, SIZE_T cbLength);

VOID DynamicArrayDestroy(PDYNAMIC_ARRAY pArray);

BOOLEAN
DynamicArrayIsEmpty(PDYNAMIC_ARRAY pArray);

PVOID
DynamicArrayGet(PDYNAMIC_ARRAY pArray, SIZE_T *pcbSize);

VOID HIDAppend1(PDYNAMIC_ARRAY pArray, UCHAR tag);

VOID HIDAppend2(PDYNAMIC_ARRAY pArray, UCHAR tag, LONG value);

BOOLEAN
DecodeNextBit(PUCHAR pBitmap, PUCHAR pValue);

BOOLEAN InputCfgDataHasBit(PVIRTIO_INPUT_CFG_DATA pData, ULONG bit);

VOID GetAbsAxisInfo(PINPUT_DEVICE pContext, ULONG uAbsAxis, struct virtio_input_absinfo *pAbsInfo);

NTSTATUS
RegisterClass(PINPUT_DEVICE pContext, PINPUT_CLASS_COMMON pClass);

NTSTATUS
HIDMouseProbe(PINPUT_DEVICE pContext,
              PDYNAMIC_ARRAY pHidDesc,
              PVIRTIO_INPUT_CFG_DATA pRelAxes,
              PVIRTIO_INPUT_CFG_DATA pAbsAxes,
              PVIRTIO_INPUT_CFG_DATA pButtons);

NTSTATUS
HIDKeyboardProbe(PINPUT_DEVICE pContext,
                 PDYNAMIC_ARRAY pHidDesc,
                 PVIRTIO_INPUT_CFG_DATA pKeys,
                 PVIRTIO_INPUT_CFG_DATA pLeds);

NTSTATUS
HIDConsumerProbe(PINPUT_DEVICE pContext, PDYNAMIC_ARRAY pHidDesc, PVIRTIO_INPUT_CFG_DATA pKeys);

NTSTATUS
HIDTabletProbe(PINPUT_DEVICE pContext,
               PDYNAMIC_ARRAY pHidDesc,
               PVIRTIO_INPUT_CFG_DATA pAxes,
               PVIRTIO_INPUT_CFG_DATA pButtons,
               PVIRTIO_INPUT_CFG_DATA pMisc);

NTSTATUS
HIDJoystickProbe(PINPUT_DEVICE pContext,
                 PDYNAMIC_ARRAY pHidDesc,
                 PVIRTIO_INPUT_CFG_DATA pAxes,
                 PVIRTIO_INPUT_CFG_DATA pButtons);

static inline PVOID VIOInputAlloc(SIZE_T cbNumberOfBytes)
{
    PVOID pPtr = ExAllocatePoolUninitialized(NonPagedPool, cbNumberOfBytes, VIOINPUT_DRIVER_MEMORY_TAG);
    if (pPtr)
    {
        RtlZeroMemory(pPtr, cbNumberOfBytes);
    }
    return pPtr;
}

static inline VOID VIOInputFree(PVOID *pPtr)
{
    if (*pPtr)
    {
        ExFreePoolWithTag(*pPtr, VIOINPUT_DRIVER_MEMORY_TAG);
        *pPtr = NULL;
    }
}
